package cn.flying.cloud.flow.core.service;

import javax.annotation.Resource;

import java.io.InputStream;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.job.api.DeadLetterJobQuery;
import org.flowable.job.api.Job;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import cn.flying.cloud.base.common.constants.Constants;
import cn.flying.cloud.base.common.exception.BizException;
import cn.flying.cloud.base.common.utils.Ft;
import cn.flying.cloud.base.common.vo.PageRequest;
import cn.flying.cloud.base.common.vo.PageResponse;
import cn.flying.cloud.flow.bo.FwProcExceptionBo;
import cn.flying.cloud.flow.bo.FwProcFormBo;
import cn.flying.cloud.flow.bo.FwProcInstBo;
import cn.flying.cloud.flow.bo.FwProcNodeBo;
import cn.flying.cloud.flow.bo.FwTaskHisBo;
import cn.flying.cloud.flow.core.constants.FlowableConstant;
import cn.flying.cloud.flow.core.enums.FlowStateEnum;
import cn.flying.cloud.flow.core.enums.TaskStateEnum;
import cn.flying.cloud.flow.model.FlowAuditVo;
import cn.flying.cloud.flow.model.FlowStartVo;
import cn.flying.cloud.flow.model.ProcException;
import cn.flying.cloud.flow.model.ProcHisInstance;
import cn.flying.cloud.flow.model.ProcHisTask;
import cn.flying.cloud.flow.model.ProcInstance;
import cn.flying.cloud.flow.model.ProcTask;
import cn.flying.cloud.flow.service.FwProcExceptionService;
import cn.flying.cloud.flow.service.FwProcFormService;
import cn.flying.cloud.flow.service.FwProcInstService;
import cn.flying.cloud.flow.service.FwProcNodeService;
import cn.flying.cloud.flow.service.FwTaskHisService;

/**
 * 工作流处理服务
 *
 * @author: admin
 * @date: 2022年12月17日 20:47
 * @version: 1.0
 */
@Service
public class ProcessManageService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private ProcessEngine processEngine;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ManagementService managementService;
    @Resource
    private TaskService taskService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private HistoryService historyService;
    @Resource
    private FwProcNodeService procNodeService;
    @Resource
    private FwProcInstService procInstService;
    @Resource
    private FwTaskHisService taskHisService;
    @Resource
    private FwProcFormService procFormService;
    @Resource
    private FwProcExceptionService procExceptionService;

    /**
     * 分页查询流程实例
     *
     * @param request
     * @return
     */
    public PageResponse<ProcInstance> getProcessInstancePageList(PageRequest<ProcInstance> request) {
        ProcInstance param = request.getParam();
        ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery();
        if (Ft.String.isNotBlank(param.getTenantId())) {
            query.processInstanceTenantId(param.getTenantId());
        }
        if (Ft.String.isNotBlank(param.getBizKey())) {
            query.processInstanceBusinessKey(param.getBizKey());
        }
        if (Ft.String.isNotBlank(param.getProcInstId())) {
            query.processInstanceId(param.getProcInstId());
        }
        if (Ft.String.isNotBlank(param.getProcDefId())) {
            query.processDefinitionId(param.getProcDefId());
        }
        if (Ft.String.isNotBlank(param.getStartUserId())) {
            query.startedBy(param.getStartUserId());
        }
        if (Ft.String.isNotBlank(param.getProcDefKey())) {
            query.processDefinitionKey(param.getProcDefKey());
        }
        long count = query.count();
        if (count == 0) {
            new PageResponse<ProcInstance>(count, request.getPageNum(), new ArrayList<>());
        }
        List<ProcessInstance> instances = query.orderByStartTime().desc().listPage((request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());
        List<ProcInstance> list = instances.stream().map(instance -> {
            ProcInstance inst = new ProcInstance();
            inst.setProcDefId(instance.getProcessDefinitionId());
            inst.setProcDefKey(instance.getProcessDefinitionKey());
            inst.setProcDefName(instance.getProcessDefinitionName());
            inst.setProcInstId(instance.getProcessInstanceId());
            inst.setBusinessStatus(instance.getBusinessStatus());
            inst.setActivityId(instance.getActivityId());
            inst.setBizKey(instance.getBusinessKey());
            inst.setDuration(System.currentTimeMillis() - instance.getStartTime().getTime());
            inst.setTenantId(instance.getTenantId());
            inst.setStartUserId(instance.getStartUserId());
            inst.setStartTime(instance.getStartTime());
            inst.setDeploymentId(instance.getDeploymentId());
            inst.setDescription(instance.getDescription());

            return inst;
        }).collect(Collectors.toList());

        return new PageResponse<>(count, request.getPageNum(), list);
    }

    /**
     * 分页查询流程实例历史
     *
     * @param request
     * @return
     */
    public PageResponse<ProcHisInstance> getProcessHisInstancePageList(PageRequest<ProcHisInstance> request) {
        ProcHisInstance param = request.getParam();
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
        if (Ft.String.isNotBlank(param.getProcInstId())) {
            query.processInstanceId(param.getProcInstId());
        }
        if (Ft.String.isNotBlank(param.getTenantId())) {
            query.processInstanceTenantId(param.getTenantId());
        }
        if (Ft.String.isNotBlank(param.getBizKey())) {
            query.processInstanceBusinessKey(param.getBizKey());
        }
        if (Ft.String.isNotBlank(param.getProcDefId())) {
            query.processDefinitionId(param.getProcDefId());
        }
        if (Ft.String.isNotBlank(param.getProcDefKey())) {
            query.processDefinitionKey(param.getProcDefKey());
        }
        if (Ft.String.isNotBlank(param.getStartUserId())) {
            query.startedBy(param.getStartUserId());
        }
        long count = query.count();
        if (count == 0) {
            new PageResponse<ProcHisInstance>(count, request.getPageNum(), new ArrayList<>());
        }
        List<HistoricProcessInstance> historicProcessInstances = query.orderByProcessInstanceStartTime().desc()
                .listPage((request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());
        List<ProcHisInstance> list = historicProcessInstances.stream().map(inst -> {
            ProcHisInstance instance = new ProcHisInstance();
            instance.setProcDefId(inst.getProcessDefinitionId());
            instance.setProcDefKey(inst.getProcessDefinitionKey());
            instance.setProcDefName(inst.getProcessDefinitionName());
            instance.setProcInstId(inst.getId());
            instance.setBizKey(inst.getBusinessKey());
            instance.setStartTime(inst.getStartTime());
            instance.setEndTime(inst.getEndTime());
            instance.setDuration(inst.getDurationInMillis());
            if (inst.getDurationInMillis() == null) {
                instance.setDuration(System.currentTimeMillis() - inst.getStartTime().getTime());
            }
            instance.setTenantId(inst.getTenantId());
            instance.setStartUserId(inst.getStartUserId());
            instance.setDeleteReason(inst.getDeleteReason());
            instance.setBusinessStatus(inst.getBusinessStatus());

            return instance;
        }).collect(Collectors.toList());

        return new PageResponse<>(count, request.getPageNum(), list);
    }

    /**
     * 启动流程
     * 如果传入流程实例ID参数，则表示重新发起
     * 如果传入流程定义ID参数，则表示启动指定的流程定义
     * 默认是根据流程定义key获取最新版本启动
     *
     * @param process
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String startProcess(FlowStartVo process) {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(process.getProcDefKey()).latestVersion().singleResult();
        if (definition == null) {
            throw new BizException("流程定义KEY【" + process.getProcDefKey() + "】不存在！");
        }
        // 判断是撤回重新发起的还是直接新增发起的
        if (Ft.String.isNotBlank(process.getProcInstId())) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(process.getProcInstId()).singleResult();
            if (FlowStateEnum.Cancel.getCode().equals(processInstance.getBusinessStatus())) {
                // 撤回的流程，重新发起
                runtimeService.activateProcessInstanceById(processInstance.getProcessInstanceId());
                // 同时修改流程状态，撤回
                runtimeService.updateBusinessStatus(processInstance.getProcessInstanceId(), FlowStateEnum.Waiting.getCode());
                // 此时流程的监听不会回调，故需要手动处理
                FwProcInstBo inst = new FwProcInstBo();
                inst.setProcInstId(process.getProcInstId());
                inst = procInstService.getOneObj(inst);
                inst.setProcStatus(FlowStateEnum.Waiting.getCode());
                procInstService.updateObj(inst);
                logger.info("流程启动成功！流程实例ID：【{}】，租户：【{}】，业务流水号：【{}】", processInstance.getProcessInstanceId(), process.getTenantId(), process.getBizKey());
                return processInstance.getProcessInstanceId();
            }
        }

        // 会set到start_user_id
        Authentication.setAuthenticatedUserId(process.getStartUserId());
        ProcessInstanceBuilder instanceBuilder = runtimeService.createProcessInstanceBuilder();
        if (Ft.String.isNotBlank(process.getProcDefId())) {
            instanceBuilder = instanceBuilder.processDefinitionId(process.getProcDefId());
        } else {
            instanceBuilder = instanceBuilder.processDefinitionKey(process.getProcDefKey());
        }
        instanceBuilder = instanceBuilder.name(Ft.String.isBlank(process.getName()) ? definition.getName() : process.getName()).businessKey(process.getBizKey()).variables(process.getVariables());
        if (Ft.String.isNotBlank(process.getTenantId())) {
            instanceBuilder = instanceBuilder.tenantId(process.getTenantId());
        }
        ProcessInstance processInstance = instanceBuilder.start();
        Authentication.setAuthenticatedUserId(null);

        // TODO 如果第一个节点发起人节点，需要自动审批处理

        logger.info("流程启动成功！流程实例ID：【{}】，租户：【{}】，业务流水号：【{}】", processInstance.getProcessInstanceId(), process.getTenantId(), process.getBizKey());
        return processInstance.getProcessInstanceId();
    }

    /**
     * 启动流程
     * 根据流程定义key启动，默认执行最新版本的流程定义
     *
     * @param bizKey
     * @param processDefKey
     * @param variables
     * @param userId
     * @param tenantId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String startProcess(String bizKey, String processDefKey, Map<String, Object> variables, String userId, String tenantId) {
        long count = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefKey).count();
        if (count == 0) {
            throw new BizException("流程定义KEY【" + processDefKey + "】不存在！");
        }

        // 获取当前用户设置到flowable中，会set到start_user_id
        Authentication.setAuthenticatedUserId(userId);
        ProcessInstance processInstance;
        if (Ft.String.isNotBlank(tenantId)) {
            processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(processDefKey, bizKey, variables, tenantId);
        } else {
            processInstance = runtimeService.startProcessInstanceByKey(processDefKey, bizKey, variables);
        }
        Authentication.setAuthenticatedUserId(null);
        logger.info("流程启动成功！流程实例ID：【{}】，租户：【{}】，业务流水号：【{}】", processInstance.getProcessInstanceId(), tenantId, bizKey);
        return processInstance.getProcessInstanceId();
    }

    /**
     * 流程撤回
     *
     * @param procInstId
     */
    @Transactional(rollbackFor = Exception.class)
    public void cancelProcess(String procInstId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
        if (processInstance == null) {
            throw new BizException("没有查询到ID【" + procInstId + "】的流程实例");
        }
        String businessStatus = processInstance.getBusinessStatus();
        if (!FlowStateEnum.Waiting.getCode().equals(businessStatus)) {
            throw new BizException("流程已审批不能撤回！");
        }
        if (processInstance.isSuspended()) {
            throw new BizException("流程处理挂起状态！");
        }
        runtimeService.suspendProcessInstanceById(processInstance.getProcessInstanceId());
        // 同时修改流程状态，撤回
        runtimeService.updateBusinessStatus(processInstance.getProcessInstanceId(), FlowStateEnum.Cancel.getCode());

        // 同时修改流程状态，撤回操作只是修改流程的状态，不会触发流程的监听器，故这里要做状态修改操作
        FwProcInstBo inst = new FwProcInstBo();
        inst.setProcInstId(procInstId);
        inst = procInstService.getOneObj(inst);
        inst.setProcStatus(FlowStateEnum.Cancel.getCode());
        procInstService.updateObj(inst);
        logger.info("流程撤回成功！流程实例ID：【{}】，租户：【{}】，业务流水号：【{}】", processInstance.getProcessInstanceId(), processInstance.getTenantId(), processInstance.getBusinessKey());
    }

    /**
     * 流程作废（删除实例）
     *
     * @param procInstId
     * @param reason
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteProcess(String procInstId, String reason) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
        if (processInstance == null) {
            throw new BizException("没有查询到ID【" + procInstId + "】的流程实例");
        }
        runtimeService.deleteProcessInstance(processInstance.getProcessInstanceId(), Ft.String.isBlank(reason) ? "作废删除" : reason);
        logger.info("流程删除成功！流程实例ID：【{}】，租户：【{}】，业务流水号：【{}】", processInstance.getProcessInstanceId(), processInstance.getTenantId(), processInstance.getBusinessKey());
    }

    /**
     * 查询流程实例任务
     *
     * @param request
     * @return
     */
    public PageResponse<ProcTask> processTask(PageRequest<ProcTask> request) {
        ProcTask param = request.getParam();
        TaskQuery query = taskService.createTaskQuery().processInstanceId(param.getProcInstId());
        if (Ft.String.isNotBlank(param.getTenantId())) {
            query.taskTenantId(param.getTenantId());
        }
        long count = query.count();
        if (count == 0) {
            return new PageResponse<>(count, request.getPageNum(), new ArrayList<>());
        }
        List<Task> list = query.orderByTaskCreateTime().desc()
                .listPage((request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());
        List<ProcTask> tasks = convertTasks(list);
        return new PageResponse<>(count, request.getPageNum(), tasks);
    }

    /**
     * 审批流转历史
     *
     * @param request
     * @return
     */
    public PageResponse<ProcHisTask> getProcTaskHis(PageRequest<ProcHisTask> request) {
        ProcHisTask param = request.getParam();
        HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().processInstanceId(param.getProcInstId()).taskWithoutDeleteReason();
        long count = query.count();
        if (count == 0) {
            return new PageResponse<>(count, request.getPageNum(), new ArrayList<>());
        }
        List<HistoricTaskInstance> hisTasks = query.orderByTaskCreateTime().desc()
                .listPage((request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());
        List<ProcHisTask> list = convertTaskHis(hisTasks);

//        String sql = "SELECT RES.* from ACT_HI_TASKINST RES WHERE RES.PROC_INST_ID_ = '"+procInstId+"' and RES.END_TIME_ is not null and (RES.DELETE_REASON_ != 'MI_END' or RES.DELETE_REASON_ is null or RES.DELETE_REASON_ = '') order by RES.START_TIME_ desc";
//        List<HistoricTaskInstance> hisTasks = historyService.createNativeHistoricTaskInstanceQuery().sql(sql).list();

        return new PageResponse<>(count, request.getPageNum(), list);
    }

    private List<ProcHisTask> convertTaskHis(List<HistoricTaskInstance> list) {
        if (CollectionUtils.isEmpty(list)) {
            list = new ArrayList<>();
        }
        return list.stream().filter(t -> !"MI_END".equals(t.getDeleteReason())).map(t -> {
            ProcHisTask task = new ProcHisTask();
            task.setTaskId(t.getId());
            task.setTaskName(t.getName());
            task.setTaskDefKey(t.getTaskDefinitionKey());
            task.setFormKey(t.getFormKey());
            if (StringUtils.isEmpty(t.getFormKey())) {
                task.setFormKey(t.getTaskDefinitionKey());
            }
            task.setExecutionId(t.getExecutionId());
            task.setOwner(t.getOwner());
            task.setAssignee(t.getAssignee());
            task.setProcDefId(t.getProcessDefinitionId());
            task.setProcInstId(t.getProcessInstanceId());
            HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult();
            task.setProcDefKey(instance.getProcessDefinitionKey());
            task.setBizKey(instance.getBusinessKey());
            task.setName(instance.getName());
            task.setStartUserId(instance.getStartUserId());
            task.setTenantId(t.getTenantId());
            task.setClaimTime(t.getClaimTime());
            task.setCreateTime(t.getCreateTime());
            task.setEndTime(t.getEndTime());
            task.setDuration(t.getDurationInMillis());
            if (t.getEndTime() == null && t.getDurationInMillis() == null) {
                task.setDuration(System.currentTimeMillis() - task.getCreateTime().getTime());
            }
            List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery().processInstanceId(t.getProcessInstanceId()).taskId(t.getId()).list();
            if (!CollectionUtils.isEmpty(variables)) {
                variables.forEach(v -> {
                    if (FlowableConstant.TaskState.equals(v.getVariableName())) {
                        task.setStatus(TaskStateEnum.val((String) v.getValue()));
                    }
                    if (FlowableConstant.COMMENT.equals(v.getVariableName())) {
                        task.setComment((String) v.getValue());
                    }
                });
            }
            task.setDeleteReason(t.getDeleteReason());

            return task;
        }).collect(Collectors.toList());
    }

    /**
     * 查询未分配代理人的任务（未领取）
     *
     * @param request
     * @return
     */
    public PageResponse<ProcTask> unassignedTasks(PageRequest<ProcTask> request) {
        TaskQuery taskQuery = taskService.createTaskQuery();
        ProcTask param = request.getParam();
        if (Ft.String.isNotBlank(param.getTenantId())) {
            taskQuery.taskTenantId(param.getTenantId());
        }
        if (Ft.String.isNotBlank(param.getProcDefKey())) {
            taskQuery.processDefinitionKey(param.getProcDefKey());
        }
        if (Ft.String.isNotBlank(param.getProcDefId())) {
            taskQuery.processDefinitionId(param.getProcDefId());
        }
        if (Ft.String.isNotBlank(param.getBizKey())) {
            taskQuery.processInstanceBusinessKeyLike(param.getBizKey());
        }
        long count = taskQuery.taskUnassigned().count();
        if (count == 0) {
            return new PageResponse<>(count, request.getPageNum(), new ArrayList<>());
        }
        List<Task> list = taskQuery.taskUnassigned()
                .orderByTaskCreateTime().desc()
                .listPage((request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());
        List<ProcTask> tasks = convertTasks(list);
        return new PageResponse<>(count, request.getPageNum(), tasks);
    }

    private List<ProcTask> convertTasks(List<Task> list) {
        if (CollectionUtils.isEmpty(list)) {
            list = new ArrayList<>();
        }
        return list.stream().map(t -> {
            return convertTask(t);
        }).collect(Collectors.toList());
    }

    private ProcTask convertTask(Task task) {
        ProcTask procTask = new ProcTask();
        procTask.setTaskId(task.getId());
        procTask.setTaskName(task.getName());
        procTask.setTaskType("");
        procTask.setFormKey(task.getFormKey());
        if (StringUtils.isEmpty(task.getFormKey())) {
            procTask.setFormKey(task.getTaskDefinitionKey());
        }
        procTask.setTaskDefKey(task.getTaskDefinitionKey());
        procTask.setExecutionId(task.getExecutionId());
        procTask.setAssignee(task.getAssignee());
        procTask.setProcDefId(task.getProcessDefinitionId());
        procTask.setProcInstId(task.getProcessInstanceId());
        procTask.setTenantId(task.getTenantId());
        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        procTask.setProcDefKey(instance.getProcessDefinitionKey());
        procTask.setName(instance.getName());
        procTask.setBizKey(instance.getBusinessKey());
        procTask.setStartUserId(instance.getStartUserId());
        procTask.setClaimTime(task.getClaimTime());
        procTask.setCreateTime(task.getCreateTime());
        procTask.setDuration(System.currentTimeMillis() - task.getCreateTime().getTime());

        return procTask;
    }

    /**
     * 领取（代理）任务
     *
     * @param taskId
     * @param assignee
     */
    @Transactional(rollbackFor = Exception.class)
    public void claimTask(String taskId, String assignee) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new BizException("没有查询到任务【" + taskId + "】");
        }
        taskService.claim(taskId, assignee);

        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();

        saveTaskHis(task, instance, null, "Claim", "【" + Ft.Local.UserName() + "】领取任务");
        logger.info("用户【{}】领取任务【{}】成功！", assignee, taskId);
    }


    /**
     * 归还领取的（代理）任务
     *
     * @param taskId
     * @param assignee
     */
    @Transactional(rollbackFor = Exception.class)
    public void unclaimTask(String taskId, String assignee) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new BizException("没有查询到任务【" + taskId + "】");
        }
        taskService.unclaim(taskId);
        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();

        saveTaskHis(task, instance, null, "Unclaim", "【" + Ft.Local.UserName() + "】归还任务");
        logger.info("用户【{}】归还任务【{}】成功！", assignee, taskId);
    }

    /**
     * 查询我的待办任务（正在进行的任务）
     *
     * @param request
     * @return
     */
    public PageResponse<ProcTask> incompleteTasks(PageRequest<ProcTask> request) {
        ProcTask param = request.getParam();
        TaskQuery taskQuery = taskService.createTaskQuery();
        if (Ft.String.isNotBlank(param.getProcDefKey())) {
            taskQuery.processDefinitionKey(param.getProcDefKey());
        }
        if (Ft.String.isNotBlank(param.getTenantId())) {
            taskQuery.taskTenantId(param.getTenantId());
        }
        if (Ft.String.isNotBlank(param.getProcDefId())) {
            taskQuery.processDefinitionId(param.getProcDefId());
        }
        if (Ft.String.isNotBlank(param.getBizKey())) {
            taskQuery.processInstanceBusinessKeyLike(param.getBizKey());
        }
        long count = taskQuery.taskAssignee(param.getAssignee()).count();
        if (count == 0) {
            return new PageResponse<>(count, request.getPageNum(), new ArrayList<>());
        }
        List<Task> list = taskQuery.taskAssignee(param.getAssignee())
                .orderByTaskCreateTime().desc()
                .listPage((request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());
        List<ProcTask> tasks = convertTasks(list);
        return new PageResponse<>(count, request.getPageNum(), tasks);
    }

    /**
     * 完成提交任务
     *
     * @param auditVo
     */
    @Transactional(rollbackFor = Exception.class)
    public void completeTask(FlowAuditVo auditVo) {
        String taskId = auditVo.getTaskId();
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new BizException("没有查询到任务【" + taskId + "】");
        }
        String userId = Ft.Local.UserId();
        String userName = Ft.Local.UserName();
        if (!userId.equals(task.getAssignee())) {
            throw new BizException("没有提交当前任务权限");
        }
        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        if (Ft.String.isNotBlank(auditVo.getComment())) {
            Authentication.setAuthenticatedUserId(userId);
            taskService.addComment(taskId, task.getProcessInstanceId(), auditVo.getComment());
            Authentication.setAuthenticatedUserId(null);
        }
        Map<String, Object> variables = auditVo.getVariables();
        if (variables == null) {
            variables = new HashMap<>();
        }
        variables.put(FlowableConstant.TaskState, auditVo.getTaskState());
        variables.put(FlowableConstant.COMMENT, auditVo.getComment());

        taskService.setVariablesLocal(taskId, variables);

        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            // 表示是委托的任务，需要一个解委托的操作（老外的思维方式），解了之后是任务是回到了原来的处理人，并非直接完成提交当前任务，还需要complete操作
            taskService.resolveTask(taskId, variables);
        }
        // 先提交当前任务节点
        taskService.complete(taskId, variables);

        if (TaskStateEnum.D.getCode().equals(auditVo.getTaskState())) { // 驳回 默认是驳回到流程发起人
            FwProcNodeBo node = new FwProcNodeBo();
            node.setProcDefId(task.getProcessDefinitionId());
            Optional<FwProcNodeBo> min = procNodeService.getList(node).stream().min(Comparator.comparing(FwProcNodeBo::getId));
            if (!min.isPresent()) {
                throw new BizException("流程异常");
            }
            node = min.get();

            // 获取所有活动的实例的
            List<String> executionIds = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId())
                    .list()
                    .stream()
                    .map(TaskInfo::getExecutionId)
                    .collect(Collectors.toList());

            runtimeService.createChangeActivityStateBuilder()
                    //指定流程实例ID
                    .processInstanceId(task.getProcessInstanceId())
                    //指定源节点和目标节点
//                    .moveActivityIdTo(task.getTaskDefinitionKey(), node.getNodeId())
                    // 如果是并行网关可能存在多条线并行
                    .moveExecutionsToSingleActivityId(executionIds, node.getNodeId())
                    .changeState();
        }
        if (TaskStateEnum.R.getCode().equals(auditVo.getTaskState()) || TaskStateEnum.F.getCode().equals(auditVo.getTaskState())) { // 拒绝和失败直接跳转到最后一个节点，结束流程
            // 设置流程变量
//            runtimeService.setVariables(task.getProcessInstanceId(), variables);

            // 获取所有活动的实例的
            List<String> activityIds = runtimeService.createExecutionQuery()
                    .processInstanceId(task.getProcessInstanceId()).parentId(task.getProcessInstanceId())
                    .list()
                    .stream()
                    .map(Execution::getActivityId)
                    .collect(Collectors.toList());
//            List<String> executionIds = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId())
//                    .list()
//                    .stream()
//                    .map(TaskInfo::getExecutionId)
//                    .collect(Collectors.toList());

            // 这里采用系统内置默认，也就是以后流程图绘制的时候最后一个节点必须为这个
            String endNodeId = "CloseLoopHandling";
            runtimeService.createChangeActivityStateBuilder()
                    //指定流程实例ID
                    .processInstanceId(task.getProcessInstanceId())
                    //指定源节点和目标节点
//                    .moveActivityIdTo(task.getTaskDefinitionKey(), endNodeId)
                    // 如果是并行网关可能存在多条线并行
//                    .moveExecutionsToSingleActivityId(executionIds, endNodeId)
                    .moveActivityIdsToSingleActivityId(activityIds, endNodeId)
                    .changeState();
        }

        saveTaskHis(task, instance, auditVo.getTaskState(), "Audit", Ft.String.isNotBlank(auditVo.getComment()) ? auditVo.getComment() : "【" + userName + "】提交任务");
        logger.info("任务【{}】提交成功！", taskId);
    }

    private void saveTaskHis(Task task, ProcessInstance instance, String result, String opType, String comment) {
        FwTaskHisBo taskHis = new FwTaskHisBo();
        taskHis.setProcInstId(task.getProcessInstanceId());
        taskHis.setProcInstName(instance.getName());
        taskHis.setBizKey(instance.getBusinessKey());
        taskHis.setStartUserId(instance.getStartUserId());
        taskHis.setTaskId(task.getId());
        taskHis.setTaskKey(task.getTaskDefinitionKey());
        taskHis.setTaskName(task.getName());
        taskHis.setUserId(Ft.Local.UserId());
        taskHis.setUserName(Ft.Local.UserName());
        taskHis.setResult(result);
        taskHis.setOpType(opType);
        if (task.getClaimTime() != null) {
            taskHis.setClaimTime(task.getClaimTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
        }
        taskHis.setStartTime(task.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
        taskHis.setEndTime(LocalDateTime.now());
        taskHis.setDuration(Duration.between(taskHis.getStartTime(), taskHis.getEndTime()).toMillis() + "");
        taskHis.setRemark(comment);
        taskHis.setProcDefId(task.getProcessDefinitionId());
        taskHis.setProcDefKey(instance.getProcessDefinitionKey());
        taskHis.setCreatedBy(Ft.Local.UserId());
        taskHis.setCreatedTime(LocalDateTime.now());
        taskHis.setUpdatedBy(Ft.Local.UserId());
        taskHis.setUpdatedTime(LocalDateTime.now());
        taskHis.setTenantId((String) Ft.ThreadLocal.get(Constants.TENANT_ID));
        taskHisService.createObj(taskHis);
    }

    /**
     * 查询我的已办任务
     *
     * @param request
     * @return
     */
    public PageResponse<ProcHisTask> completedHisTasks(PageRequest<ProcHisTask> request) {
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
        ProcHisTask param = request.getParam();
        if (Ft.String.isNotBlank(param.getTenantId())) {
            historicTaskInstanceQuery.taskTenantId(param.getTenantId());
        }
        if (Ft.String.isNotBlank(param.getProcInstId())) {
            historicTaskInstanceQuery.processInstanceId(param.getProcInstId());
        }
        if (Ft.String.isNotBlank(param.getProcDefId())) {
            historicTaskInstanceQuery.processDefinitionId(param.getProcDefId());
        }
        if (Ft.String.isNotBlank(param.getProcDefKey())) {
            historicTaskInstanceQuery.processDefinitionKey(param.getProcDefKey());
        }
        if (Ft.String.isNotBlank(param.getBizKey())) {
            historicTaskInstanceQuery.processInstanceBusinessKeyLike(param.getBizKey());
        }
        long count = historicTaskInstanceQuery.taskAssignee(param.getAssignee()).finished().count();
        if (count == 0) {
            return new PageResponse<>(count, request.getPageNum(), new ArrayList<>());
        }
        List<HistoricTaskInstance> hisTasks = historicTaskInstanceQuery.taskAssignee(param.getAssignee()) // 查询指定用户的任务
                .taskWithoutDeleteReason()
                .finished()  // 只查询已完成的任务
                .orderByHistoricTaskInstanceEndTime()  // 按结束时间排序
                .desc()  // 降序排列
                .listPage((request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());

        List<ProcHisTask> list = convertTaskHis(hisTasks);
        return new PageResponse<>(count, request.getPageNum(), list);
    }

    /**
     * 触发任务（针对Receive Task）
     *
     * @param processInstId
     * @param activityId
     */
    @Transactional(rollbackFor = Exception.class)
    public void triggerTask(String processInstId, String activityId) {
        Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstId).activityId(activityId).singleResult();
        runtimeService.trigger(execution.getId());
        logger.info("成功触发任务【{}】执行！", activityId);
    }

    /**
     * 获取流程任务详情
     *
     * @param processInstId
     * @param assignee
     */
    @Transactional(rollbackFor = Exception.class)
    public ProcTask detailTask(String processInstId, String assignee) {
        Task task = taskService.createTaskQuery().processInstanceId(processInstId).taskAssignee(assignee).singleResult();
        if (task == null) {
            return new ProcTask();
        }
        ProcTask procTask = convertTask(task);
        FwProcFormBo form = new FwProcFormBo();
        form.setProcDefId(procTask.getProcDefId());
        form.setFormKey(procTask.getFormKey());
        form = procFormService.getOneObj(form);
        procTask.setFormBtn(form.getFormBtn());
        return procTask;
    }

    /**
     * 委派任务
     *
     * @param taskId
     * @param assignee
     */
    @Transactional(rollbackFor = Exception.class)
    public void delegateTask(String taskId, String assignee) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new BizException("没有查询到任务【" + taskId + "】");
        }
        // 判断是否为同一个人，避免重复分配，导致资源浪费，以及审批流程的混乱和延迟
        if (!assignee.equals(task.getAssignee())) {
            // 会将该任务的原来assignee设置到owner中，DELEGATION_字段值为PENDING
            taskService.delegateTask(taskId, assignee);
        }

        // 保存一条操作记录
        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();

        saveTaskHis(task, instance, null, "Delegate", "委托任务给【" + Ft.Local.UserName() + "】");
        logger.info("任务【{}】将委派给用户【{}】！", taskId, assignee);
    }

    /**
     * 指派（转办、转签）任务（分配任务处理人） 转签就是将任务的负责人直接设置为别人。即本来由自己办理，改为别人办理。
     *
     * @param taskId
     * @param assignee
     */
    @Transactional(rollbackFor = Exception.class)
    public void assignTask(String taskId, String assignee) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new BizException("没有查询到任务【" + taskId + "】");
        }
        // 判断是否为同一个人，避免重复分配，导致资源浪费，以及审批流程的混乱和延迟
        if (!assignee.equals(task.getAssignee())) {
            taskService.setOwner(taskId, task.getAssignee());
            taskService.setAssignee(taskId, assignee);
            // 保存一条操作记录
            ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();

            saveTaskHis(task, instance, null, "Assign", "任务转办【" + Ft.Local.UserName() + "】");
            logger.info("任务【{}】分配用户【{}】！", taskId, assignee);
        }
    }

    /**
     * 添加任务处理人
     *
     * @param taskId
     * @param assignee
     */
    @Transactional(rollbackFor = Exception.class)
    public void addSignTask(String taskId, String assignee) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new BizException("没有查询到任务【" + taskId + "】");
        }
        // 判断是否为同一个人，避免重复分配，导致资源浪费，以及审批流程的混乱和延迟
        if (!assignee.equals(task.getAssignee())) {
            taskService.setAssignee(taskId, assignee);
            // 保存一条操作记录
            ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();

            saveTaskHis(task, instance, null, "AddSign", "任务加签【" + Ft.Local.UserName() + "】");
            logger.info("任务【{}】加签用户【{}】！", taskId, assignee);
        }
    }

    /**
     * 为任务添加用户组
     *
     * @param taskId
     * @param groupId
     */
    @Transactional(rollbackFor = Exception.class)
    public void addCandidateGroup(String taskId, String groupId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new BizException("没有查询到任务【" + taskId + "】");
        }
        taskService.addCandidateGroup(taskId, groupId);
    }

    /**
     * 生成流程进度图片
     *
     * @param processInstId
     * @param tenantId
     * @return
     */
    public InputStream getInstanceDiagram(String processInstId, String tenantId) {
        // 1.获取当前的流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstId).singleResult();
        String processDefId;
        List<String> activeActivityIds = new ArrayList<>();
        List<String> highLightedFlows = new ArrayList<>();
        // 2.获取所有的历史轨迹线对象
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstId).activityType(BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW).list();
        historicActivityInstances.forEach(historicActivityInstance -> highLightedFlows.add(historicActivityInstance.getActivityId()));
        // 3. 获取流程定义id和高亮的节点id
        if (processInstance != null) {
            // 3.1 正在运行的流程实例
            processDefId = processInstance.getProcessDefinitionId();
            activeActivityIds = runtimeService.getActiveActivityIds(processInstId);
        } else {
            // 3.2 已经结束的流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(processInstId).singleResult();
            processDefId = historicProcessInstance.getProcessDefinitionId();
            // 3.3 获取结束节点列表
            List<HistoricActivityInstance> historicEnds = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(processInstId).activityType(BpmnXMLConstants.ELEMENT_EVENT_END).list();
            List<String> finalActiveActivityIds = activeActivityIds;
            historicEnds.forEach(historicActivityInstance -> finalActiveActivityIds.add(historicActivityInstance.getActivityId()));
        }
        // 4. 获取bpmnModel对象
        ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration();
        ProcessDiagramGenerator diagramGenerator = configuration.getProcessDiagramGenerator();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId);
        // 5. 生成图片流
        //        // 6. 转化成Base64网络传输
//        return "data:image/png;base64," + Base64.encode(inputStream);
        return diagramGenerator.generateDiagram(bpmnModel, "png", activeActivityIds, highLightedFlows, configuration.getActivityFontName(), configuration.getLabelFontName(), configuration.getAnnotationFontName(), this.getClass().getClassLoader(), 1.0, true);
    }

    /**
     * 查询所有的作业异常信息
     *
     * @param request
     * @return
     */
    public PageResponse<ProcException> getAllException(PageRequest<ProcException> request) {
        ProcException param = request.getParam();
        DeadLetterJobQuery query = managementService.createDeadLetterJobQuery();
        if (Ft.String.isNotBlank(param.getTenantId())) {
            query.jobTenantId(param.getTenantId());
        }
        if (Ft.String.isNotBlank(param.getProcInstId())) {
            query.processInstanceId(param.getProcInstId());
        }
        if (Ft.String.isNotBlank(param.getProcDefId())) {
            query.processDefinitionId(param.getProcDefId());
        }
        if (Ft.String.isNotBlank(param.getBizKey())) {
            ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(param.getBizKey()).singleResult();
            query.processInstanceId(instance.getProcessInstanceId());
        }
        long count = query.count();
        if (count == 0) {
            return new PageResponse<>(count, request.getPageNum(), new ArrayList<>());
        }
        List<Job> jobs = query.orderByJobCreateTime().desc()
                .listPage((request.getPageNum() - 1) * request.getPageSize(), request.getPageSize());
        List<ProcException> list = jobs.stream().map(job -> {
            ProcException exception = new ProcException();

            ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(job.getProcessInstanceId()).singleResult();
            exception.setJobId(job.getId());
            exception.setProcInstId(job.getProcessInstanceId());
            exception.setProcInstName(instance.getName());
            exception.setExecutionId(job.getExecutionId());
            exception.setProcDefId(job.getProcessDefinitionId());
            exception.setProcDefKey(instance.getProcessDefinitionKey());
            exception.setProcDefName(instance.getProcessDefinitionName());
            exception.setNodeId(job.getElementId());
            exception.setNodeName(job.getElementName());
            exception.setBizKey(instance.getBusinessKey());
            exception.setExceptionMsg(job.getExceptionMessage());
            exception.setDealState("");
            exception.setTenantId(job.getTenantId());
            exception.setCreateTime(job.getCreateTime());

            return exception;
        }).collect(Collectors.toList());

        return new PageResponse<>(count, request.getPageNum(), list);
    }

    /**
     * 查询作业异常堆栈信息
     *
     * @param jobId
     * @return
     */
    public String getExceptionTrace(String jobId) {
        return managementService.getDeadLetterJobExceptionStacktrace(jobId);
    }

    /**
     * 处理异常
     *
     * @param executionId
     */
    public void dealException(String executionId) {
        Job job = managementService.createDeadLetterJobQuery().withException().executionId(executionId).singleResult();

        FwProcExceptionBo param = new FwProcExceptionBo();
        param.setProcInstId(job.getProcessInstanceId());
        param.setExecutionId(job.getExecutionId());
        param = procExceptionService.getOneObj(param);
        param.setDealState("Y");
        procExceptionService.updateObj(param);

        //加入执行，重试3次
        managementService.moveDeadLetterJobToExecutableJob(job.getId(), 3);
    }

}
